﻿using System.Collections.Generic;
using UnityEngine;

namespace AI.Graph {


    public static class NodeUtility {

        /// <summary>
        /// Returns true if the node is an entry state.
        /// </summary>
        public static bool IsEntryState (this Node node) {
            return node.IsTypeOf<EntryState> ();
        }


        /// <summary>
        /// Returns true if the node is an any state.
        /// </summary>
        public static bool IsAnyState (this Node node) {
            return node.IsTypeOf<AnyState> ();
        }


        /// <summary>
        /// Returns true if the node is state.
        /// </summary>
        public static bool IsState (this Node node) {
            return node.IsTypeOf<State> ();
        }


        /// <summary>
        /// Returns true if the node is a condition.
        /// </summary>
        public static bool IsCondition (this Node node) {
            return node.IsTypeOf<Condition> ();
        }


        /// <summary>
        /// Returns true if the node is a sub state.
        /// </summary>
        public static bool IsSubState (this Node node) {
            return node.IsTypeOf<SubState> ();
        }


        /// <summary>
        /// Returns true if the node is a sub condition.
        /// </summary>
        public static bool IsSubCondition (this Node node) {
            return node.IsTypeOf<SubCondition> ();
        }


        /// <summary>
        /// Find all connected Nodes on the given port.
        /// </summary>
        public static void FindAllConnectedNodesOnPort (ConnectionPort port, List<Node> connectedNodes) {
            if (port == null) {
                Debug.LogError ("the given port is null");
                return;
            }

            Node baseNode = port.node;
            ConnectionPort[] connectedPorts = port.connectedPorts;

            for (int i = 0; i < connectedPorts.Length; i++) {
                ConnectionPort connectedPort = connectedPorts[i];
                Node connectedNode = connectedPort.node;

                FindAllConnectedNodesOnPort (baseNode, baseNode, connectedNode, connectedNodes);
            }
        }


        /// <summary>
        /// Stores all connected Nodes in the given List.
        /// </summary>
        static void FindAllConnectedNodesOnPort (Node baseNode, Node startNode, Node currentNode, List<Node> connectedNodes) {
            if (currentNode == baseNode) {
                return;
            }

            if (!connectedNodes.Contains (currentNode)) {
                connectedNodes.Add (currentNode);
            }

            ConnectionPort[] nodePorts = currentNode.ports;

            for (int nodePortIndex = 0; nodePortIndex < nodePorts.Length; nodePortIndex++) {
                ConnectionPort nodePort = nodePorts[nodePortIndex];
                ConnectionPort[] connectedPorts = nodePort.connectedPorts;

                for (int connectedPortIndex = 0; connectedPortIndex < connectedPorts.Length; connectedPortIndex++) {
                    ConnectionPort connectedPort = connectedPorts[connectedPortIndex];

                    Node connectedNode = connectedPort.node;

                    if (connectedNode != startNode) {
                        FindAllConnectedNodesOnPort (baseNode, currentNode, connectedNode, connectedNodes);
                    }
                }
            }
        }


        /// <summary>
        /// Find the first node of type T that is connected with the given node.
        /// Otherwise it will return null.  
        /// </summary>
        public static T FindConnectedNodeOfType<T> (Node startNode) where T : Node {
            T foundNode = null;

            ConnectionPort[] nodePorts = startNode.ports;
            for (int nodePortIndex = 0; nodePortIndex < nodePorts.Length; nodePortIndex++) {
                ConnectionPort nodePort = nodePorts[nodePortIndex];

                ConnectionPort[] connectedPorts = nodePort.connectedPorts;
                for (int connectedPortIndex = 0; connectedPortIndex < connectedPorts.Length; connectedPortIndex++) {
                    ConnectionPort connectedPort = connectedPorts[connectedPortIndex];

                    Node connectedNode = connectedPort.node;

                    FindConnectedNodeOfType<T> (startNode, connectedNode, ref foundNode);
                }
            }

            return foundNode;
        }

    
        static void FindConnectedNodeOfType<T> (Node startNode, Node currentNode, ref T foundNode) where T : Node {
            if (foundNode != null) {
                return;
            }

            if (currentNode.IsTypeOf<T> ()) {
                foundNode = (T) currentNode;
                return;
            }

            ConnectionPort[] nodePorts = currentNode.ports;
            for (int nodePortIndex = 0; nodePortIndex < nodePorts.Length; nodePortIndex++) {
                ConnectionPort nodePort = nodePorts[nodePortIndex];

                ConnectionPort[] connectedPorts = nodePort.connectedPorts;
                for (int connectedPortIndex = 0; connectedPortIndex < connectedPorts.Length; connectedPortIndex++) {
                    ConnectionPort connectedPort = connectedPorts[connectedPortIndex];

                    Node connectedNode = connectedPort.node;

                    if (connectedNode != startNode) {
                        FindConnectedNodeOfType<T> (currentNode, connectedNode, ref foundNode);
                    }
                }
            }
        }

    }


}